package com.fyl.core.utils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newScheduledThreadPool;

public class RCache<T> {

    private final Map<String, T> m_objects;
    private final Map<String, Long> m_expiredObjects;
    private final long m_lExpireTime;
    private final TimeUnit m_lExpireTimeUnit;
    private final ExecutorService m_executor;

    public RCache() {
        this(100, TimeUnit.SECONDS);
    }

    public RCache(final int nExpireTime, final TimeUnit timeUnit) {
        m_objects = new ConcurrentHashMap<>();
        m_expiredObjects = new ConcurrentHashMap<>();
        m_lExpireTime = nExpireTime;
        m_lExpireTimeUnit = timeUnit;
        m_executor = newFixedThreadPool(64);
        newScheduledThreadPool(4).scheduleWithFixedDelay(removeExpiredObjects(), m_lExpireTime / 2,
                m_lExpireTime, m_lExpireTimeUnit);
    }

    private final Runnable removeExpiredObjects() {
        return () -> {
            for (final String name : m_expiredObjects.keySet()) {
                if (System.currentTimeMillis() > m_expiredObjects.get(name)) {
                    m_executor.execute(createRemoveRunnable(name));
                }
            }
        };
    }

    private final Runnable createRemoveRunnable(final String name) {
        return () -> {
            m_objects.remove(name);
            m_expiredObjects.remove(name);
        };
    }

    public long getExpireTime() {
        return m_lExpireTime;
    }

    public void put(final String name, final T obj) {
        put(name, obj, m_lExpireTime, m_lExpireTimeUnit);
    }

    public void put(final String name, final T obj, final long expireTime, final TimeUnit timeUnit) {
        m_objects.put(name, obj);
        m_expiredObjects.put(name, System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(expireTime,timeUnit));
    }

    public T get(final String name) {
        final Long expireTime = m_expiredObjects.get(name);
        if (expireTime == null) {
            return null;
        }
        if (System.currentTimeMillis() > expireTime) {
            m_executor.execute(createRemoveRunnable(name));
            return null;
        }
        return m_objects.get(name);
    }

    @SuppressWarnings("unchecked")
    public <R extends T> R get(final String name, final Class<R> type) {
        return (R) get(name);
    }
}
